import logging
import json
import os

import numpy as np
import folium
from transliterate import translit
import plotly.graph_objects as go

from .gvkm_data_converter import GvkmDataConverter
from .opt_utils import time_from_int_to_string, time_from_string_to_int
from .constants import OPT_PATH


def get_angle(p1, p2):
    """Get angle of a line between 2 points"""
    long_diff = np.radians(p2[1] - p1[1])

    lat1 = np.radians(p1[0])
    lat2 = np.radians(p2[0])

    x = np.sin(long_diff) * np.cos(lat2)
    y = (np.cos(lat1) * np.sin(lat2) - (np.sin(lat1) * np.cos(lat2) * np.cos(long_diff)))

    bearing = np.degrees(np.arctan2(x, y))

    if bearing < 0:
        return bearing + 360
    return bearing


def draw_arrow(folium_map, p1, p2, color='blue', size=5, n_arrows=3):
    """Draw small arrows along the path from p1 to p2"""
    angle = get_angle(p1, p2) - 90

    arrow_lats = np.linspace(p1[0], p2[0], n_arrows + 2)[1:n_arrows + 1]
    arrow_lons = np.linspace(p1[1], p2[1], n_arrows + 2)[1:n_arrows + 1]

    for points in zip(arrow_lats, arrow_lons):
        folium.RegularPolygonMarker(location=points, fill_color=color, number_of_sides=3,
                                    radius=size, rotation=angle).add_to(folium_map)


def randomize_point(lat, lon):
    """Add random noise from Beta(0.3, 0.3) distribution with heavy tails to coordinates"""
    return [np.float32(lat + np.random.beta(0.3, 0.3) * 7e-4), np.float32(lon + np.random.beta(0.3, 0.3) * 7e-4)]


class Visualizer:
    def __init__(self, config_path, opt_back_path=OPT_PATH):
        self.logger = logging.getLogger('Visualizer')
        self.logger.debug(f"Initializing Visualizer")
        self.opt_back_path = opt_back_path
        try:
            with open(os.path.join(self.opt_back_path, config_path), 'r') as f:
                vizualization_params = json.load(f)
                self.pics_path = os.path.join(self.opt_back_path, vizualization_params['pics_path'])
                self.cities_path = os.path.join(self.opt_back_path, vizualization_params['cities_path'])
                self.dm_config_path = vizualization_params['dm_config_path']
                self.dm_cache_path = vizualization_params['dm_cache_path']
                self.gvkm_converter_path = vizualization_params['gvkm_converter_path']
                self.optimizer_path = vizualization_params['optimizer_path']
                self.standard_window_len = vizualization_params['standard_window_len']

        except Exception as e:
            self.logger.error(f"Couldn't load config {config_path}, {e}")
            raise FileNotFoundError("Couldn't load config")
        try:
            with open(self.cities_path, 'r') as f:
                self.cities = json.load(f)
        except Exception as e:
            self.logger.error(f"Couldn't load file {self.cities_path}, {e}")
            raise FileNotFoundError("Couldn't load cities file")

        # Color palette to draw lines on maps
        self.colors = ['#5f13e4', '#2a9d8f', '#688de8', '#f4a261', '#e76f51', '#000000', '#4cd1e0',
                       '#e63946', '#c671c3', '#457b9d', '#1dc805', '#ef476f', '#791e94',
                       '#06d6a0', '#118ab2', '#073b4c', '#614026', '#6a040f', '#9d0208', '#d00000',
                       '#e85d04', '#f48c06', '#ffba08', '#ff006e', '#8338ec', '#3a86ff', '#fb5607', '#02c39a',
                       '#ffff3f', '#fd1d53', '#8cc265', '#540b0e', '#9e2a2b', '#de73ff', '#fff3b0', '#335c67']

    def draw_time_windows(self, orders, vkm_count, save_path=None):
        """Return an html plot of order distribution by time of the day"""
        min_time = orders['time_from'].apply(time_from_string_to_int).min()
        max_time = orders['time_to'].apply(time_from_string_to_int).max() + 1

        counts = dict()
        for time in np.arange(min_time, max_time, 1):
            counts[time] = 0

        for order in orders.itertuples():
            start = time_from_string_to_int(order.time_from)
            finish = time_from_string_to_int(order.time_to)
            weight = self.standard_window_len / (finish - start)  # The longer the window the weaker the constraint
            for point in np.arange(start, finish, 1):
                counts[point] += weight

        time = []
        count = []
        for key, value in counts.items():
            time.append(key)
            count.append(value)

        fig = go.Figure()
        fig.add_trace(go.Scatter(x=time, y=count))
        fig.add_shape(type="line", x0=min_time, y0=vkm_count, x1=max_time,
                      y1=vkm_count, line=dict(color="Red", width=4), name='Количество ВКМ')

        fig.update_layout(title=f'Количество заказов по времени', template='presentation+plotly_white',
                          width=1400, height=600)
        fig.layout.yaxis.title.text = 'Заказы'
        fig.layout.xaxis.title.text = 'Время'
        ticks = ['09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00', '18:00']
        fig.update_xaxes(tickvals=[time_from_string_to_int(x) for x in ticks], ticktext=ticks)
        if save_path:
            fig.write_html(save_path)
        return fig.to_html(full_html=False, include_plotlyjs='cdn')

    @staticmethod
    def _prepare_scatter(orders, color):
        return go.Scattermapbox(
            lat=orders['lat'],
            lon=orders['lon'],
            mode='markers',
            marker=go.scattermapbox.Marker(
                symbol='circle',
                size=8,
                allowoverlap=True,
                color=color
            ),
            meta=orders['id'],
            ids=orders['id'],
            hovertext=orders['norm_addr'],
            hovertemplate='Номер заказа: %{meta}<br>Адрес: %{hovertext}',
            hoverlabel=go.scattermapbox.Hoverlabel(
                bgcolor='#ffecd1',
                bordercolor='black',
                font={
                    'family': 'Arial',
                    'size': 14,
                    'color': 'black'
                }
            )
        )


    def draw_scatter_points(self, orders, city, save_path=None):
        """Draw scatter plot of given orders"""
        orders, start_point = orders.query("norm_addr != 'start-point'"), orders.query("norm_addr == 'start-point'")

        fig = go.Figure(data=self._prepare_scatter(orders, color='#1e90ff'))
        fig.add_trace(self._prepare_scatter(start_point, color='#9acd32'))

        center = self.cities[city]['center']
        fig.update_layout(
            autosize=True,
            hovermode='closest',
            margin={'l': 0, 't': 0, 'b': 0, 'r': 0},
            mapbox={
                'style': "open-street-map",
                'center': {'lat': center['lat'], 'lon': center['lon']},
                'zoom': center['zoom']},
            template='presentation+plotly_white',
            width=1600, height=900,
            legend=dict(y=0.94)
        )
        if save_path:
            fig.write_html(save_path)
        return fig.to_html(full_html=False, include_plotlyjs='cdn')

    def draw_folium_routes(self, route, city):
        """Draw given routes on a folium map"""
        center = self.cities[city]['center']
        folium_map = folium.Map(location=[center['lat'], center['lon']], zoom_start=center['zoom'])
        color_num = 0
        for vkm in route['subroutes']:
            previous_point = np.nan
            for point in vkm['points']:
                if previous_point is np.nan:
                    previous_point = [point['lat'], point['lon']]
                else:
                    p1 = previous_point
                    p2 = randomize_point(point['lat'], point['lon'])
                    folium.PolyLine(locations=[p1, p2], color=self.colors[color_num], opacity=0.75).add_to(folium_map)
                    draw_arrow(folium_map, p1, p2, color=self.colors[color_num], n_arrows=5)

                    courier_time = '[' + point['time_at_min'] + '-' + point['time_at_max'] + ']'
                    folium.Marker(p2, tooltip=courier_time + ' ' + point['id'] + ' ' +
                                              translit(point['addr'], "ru", reversed=True)).add_to(folium_map)
                    previous_point = p2
            color_num = (color_num + 1) % len(self.colors)
        return folium_map

    def draw_plotly_routes(self, route, city):
        """Draw given routes on a plotly map"""
        fig = go.Figure()
        color_num = 0
        for vkm in route['subroutes']:
            lats, lons, ids, addresses, arrival_times, angles = [], [], [], [], [], []
            # prev_point = np.nan
            for point in vkm['points']:
                lat_rand, lon_rand = randomize_point(point['lat'], point['lon'])
                lats.append(lat_rand)
                lons.append(lon_rand)
                ids.append(point['id'])
                addresses.append(point['addr'])
                arrival_times.append('-'.join([point['time_at_min'], point['time_at_max']]))
                # if prev_point is np.nan:
                #     angles.append(0)
                # else:
                #     angles.append(get_angle((prev_point[0], prev_point[1]), (point['lat'], point['lon'])))

            fig.add_trace(go.Scattermapbox(
                name=str(vkm['deliver_id'] + 1),
                mode="markers+lines",
                lat=lats,
                lon=lons,
                meta=ids,
                ids=ids,
                hovertext=addresses,
                text=arrival_times,
                hovertemplate='Номер заказа: %{meta}<br>Адрес: %{hovertext}<br>'
                              'Ориентировочное время прибытия: %{text}',
                marker=go.scattermapbox.Marker(
                    symbol='circle',
                    color=self.colors[color_num],
                    size=8,
                    # 'angle=angles,
                    allowoverlap=True),
                line=go.scattermapbox.Line(
                    color=self.colors[color_num],
                    width=3),
                hoverlabel=go.scattermapbox.Hoverlabel(
                    bgcolor='#ffecd1',
                    bordercolor='black',
                    font={
                        'family': 'Arial',
                        'size': 14,
                        'color': 'black'
                    }
                )
            ))
            color_num = (color_num + 1) % len(self.colors)

        center = self.cities[city]['center']
        fig.update_layout(
            margin={'l': 0, 't': 0, 'b': 0, 'r': 0},
            mapbox={
                'style': "open-street-map",
                'center': {'lat': center['lat'], 'lon': center['lon']},
                'zoom': center['zoom']},
            title=f'Количество заказов по времени',
            template='presentation+plotly_white',
            width=1600, height=900,
            legend=dict(y=0.94)
        )
        return fig.to_html(full_html=False, include_plotlyjs='cdn')
